/** 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.free.i.virtual.machine.hal.devicealias.laboratory.positioningsystem;

import it.free.i.management.JmxFacilities;
import it.free.i.virtual.machine.hal.DeviceAliasRuntimeException;
import it.free.i.virtual.machine.hal.EnumerationOperationException;
import it.free.i.virtual.machine.hal.LinkingOperationException;
import it.free.i.virtual.machine.hal.devicealias.DeviceAlias;
import it.free.i.virtual.machine.hal.devicealias.DeviceAliasFacilities;
import it.free.i.virtual.machine.hal.devicealias.command.DeviceAliasCommand;
import it.free.i.virtual.machine.hal.devicealias.laboratory.positioningsystem.port.PositioningSystemCollisionDetectorPortImpl;
import it.free.i.virtual.machine.hal.devicealias.laboratory.positioningsystem.port.PositioningSystemCoordinateReaderPortImpl;
import it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.jbullet.VehicleSimulation;
import it.free.i.virtual.machine.hal.devicealias.port.DeviceAliasPort;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javabullet.ContactAddedCallback;
import javabullet.demos.opengl.LWJGL;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.Notification;
import javax.management.ReflectionException;

import org.apache.log4j.Logger;

/** 
 * This device is a laboratory test device. This means that it is dummy device whose data I/O is controlled in some way on order to test 
 * the system. All devices whose activity is not linked to a real physical hardware device, is usually a laboratory device suitable for tests.  
 * Device alias implementations are not allowed to send notifications, they can just fails when starting, and execute method via jmx calls.
 * @author stefano
 */
public class DeviceAliasLaboratoryPositioningSystemImpl implements DeviceAlias {

	private String myName = null; 
	private PositioningSystemCollisionDetectorPortImpl positioningSystemCollisionDetectorPortImpl = null; 
	private List<DeviceAliasPort> deviceAliasPortsList = null;
	private VehicleSimulation myVehicle = null;
	private static final int GUI_MAX_ACQUISITION_RETRIES = 20;
	private static final long GUI_ACQUISITION_WAIT_PER_LOOP = 1000L;
	private static Logger logger = null;
	
	static{
		logger = Logger.getLogger(DeviceAliasLaboratoryPositioningSystemImpl.class);
	}
	
	/**
	 * Default class builder
	 */
	public DeviceAliasLaboratoryPositioningSystemImpl() {
		logger.info("# device alias [class builder] call");
		logger.info("# Initializing device alias implementation: [" + this.getClass().getName() + "]");
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias 
	 */
	public void link(Properties virtualDeviceConfiguration) throws LinkingOperationException{
		myName = virtualDeviceConfiguration.getProperty(DeviceAlias.IMPL_NAME_PROP_NAME);
		logger.info("# device alias [" + this.getName() + "] [link] call");
		logger.info("# Linking device alias implementation: [" + this.getClass().getName() + "] to name: [" + this.getName() + "]");
		
		this.positioningSystemCollisionDetectorPortImpl = new PositioningSystemCollisionDetectorPortImpl();
		
		try{
			startSimulationGUI(this.positioningSystemCollisionDetectorPortImpl);
		}catch(Exception exc) {
			   logger.info("# device alias [" + this.getName() + "] [link] call, GUI activation failure");
			   throw (new LinkingOperationException(exc));
		}
		
		deviceAliasPortsList = buildDeviceAliasPortList(4);
		
		logger.info("# device alias [" + this.getName() + "] [link] call completed");
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public void reset() throws DeviceAliasRuntimeException {
		
		logger.info("#Device alias reset invoked");
	}
	
	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias 
	 */
	public void unlink() throws LinkingOperationException{
		logger.info("# device alias [" + this.getName() + "] [unlink] call");
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias 
	 */
	public List<DeviceAliasPort> getPorts() throws EnumerationOperationException{
		logger.info("# device alias [" + this.getName() + "] [get ports] call");
		return this.deviceAliasPortsList;
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias 
	 */
	public String getName() {
		return this.myName;
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public void handleNotification(Notification notification, Object handback) {
		
		boolean isValid = DeviceAliasFacilities.isCommandValid(notification);
		
		if(!isValid){
		   logger.error("Notification received is not valid");
		}
			
		//logger.info("# device alias [" + this.getName() + "] [command received], now digesting");
		DeviceAliasCommand deviceAliasCommand = (DeviceAliasCommand) notification.getUserData();
		//Execute the command switch case and then decide how to act
		digestCommand(deviceAliasCommand);
		
	}
	
	/**
	 * Process the command received
	 * @param command is the device alias command to digest
	 */
	private void digestCommand(DeviceAliasCommand command){
	
		//DeviceAliasCommandVerb verb = command.getVerb();
		
		//logger.info("# device alias [" + this.getName() + "] COMMAND VERB RECEIVED: " + verb);
		
	}
	
	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public Object getAttribute(String attribute)
			throws AttributeNotFoundException, MBeanException,
			ReflectionException {
		return null;
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public AttributeList getAttributes(String[] attributes) {
		return null;
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public MBeanInfo getMBeanInfo() {
		return JmxFacilities.buildMBeanInfo(this.getClass(), this.getName());
	}

	/**
	 * TO-DO fix me this invoke
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public Object invoke(String actionName, 
						 Object[] params, 
						 String[] signature) throws MBeanException, ReflectionException {
		
			Object invocationReturnValue = null;
		    
			try{
	            Method method = this.getClass().getMethod(actionName, new Class[params.length]);
	            invocationReturnValue = method.invoke(this, params);
	            logger.info("Invoked action [" + actionName + "] invoked method [" + method.getName() + "]");
	        }catch(NoSuchMethodException nsmExc) {    
	               throw (new MBeanException(nsmExc));
	        }catch(SecurityException sExc) {
	               throw (new MBeanException(sExc));
	        }catch(IllegalArgumentException iaExc) {
	               throw (new MBeanException(iaExc));
	        }catch(IllegalAccessException iaExc) {
	               throw (new MBeanException(iaExc));    
	        }catch(InvocationTargetException itExc) {
	               throw (new MBeanException(itExc));
	        }
	        
	        return invocationReturnValue;
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public void setAttribute(Attribute attribute)
			throws AttributeNotFoundException, InvalidAttributeValueException,
			MBeanException, ReflectionException {
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public AttributeList setAttributes(AttributeList attributes) {
		return null;
	}
	
	/**
	 * Create a list of device alias port impl
	 * @param size is the size of the list to build
	 * @return the list
	 */
	private List<DeviceAliasPort> buildDeviceAliasPortList(int size){
		
		List<DeviceAliasPort> deviceAliasPortsList = new ArrayList<DeviceAliasPort>(size);
		
		DeviceAliasPort tmpDeviceAliasPort = null;
		
		//CollisionEcho monitor
		//this.positioningSystemCollisionDetectorPortImpl = new PositioningSystemCollisionDetectorPortImpl();
		tmpDeviceAliasPort = this.positioningSystemCollisionDetectorPortImpl;
		deviceAliasPortsList.add(tmpDeviceAliasPort);
		logger.info("Registered  new port: device alias positioning collision monitor");
		
		//Coordinate reader
		tmpDeviceAliasPort = new PositioningSystemCoordinateReaderPortImpl(this.myVehicle.carChassis, this.myVehicle);
		deviceAliasPortsList.add(tmpDeviceAliasPort);
		logger.info("Registered  new port: device alias positioning system coordinate reader");
		
		
//		//Y coordinate reader
//		tmpDeviceAliasPort = new PositioningSystemYCoordinateReaderPortImpl(this.myVehicle.carChassis);
//		deviceAliasPortsList.add(tmpDeviceAliasPort);
//		logger.info("Registered  new port: device alias positioning system y coordinate reader");
//		
//		//Z coordinate reader
//		tmpDeviceAliasPort = new PositioningSystemZCoordinateReaderPortImpl(this.myVehicle.carChassis);
//		deviceAliasPortsList.add(tmpDeviceAliasPort);
//		logger.info("Registered  new port: device alias positioning system z coordinate reader");
		
		logger.info("# device alias [" + this.getName() + "] dummy list of ports created: " + deviceAliasPortsList.size());
		return deviceAliasPortsList;
	}
	
	private void startSimulationGUI(ContactAddedCallback contactAddedCallBACK) throws Exception {
		
		this.myVehicle = new VehicleSimulation(LWJGL.getGL(), contactAddedCallBACK);
		
	    Thread vehicleSimulationThread = new Thread(this.myVehicle);
	    vehicleSimulationThread.start();
	
	    /*
	    try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		*/
		for(int i=0; i < GUI_MAX_ACQUISITION_RETRIES; i++){
			if(this.myVehicle.carChassis != null){
			   logger.info("Simulation environment acquired ");
			   break;
			}else{
				  logger.info("Waiting for simulation environment, pause " + i);
				  Thread.sleep(GUI_ACQUISITION_WAIT_PER_LOOP);
			}
		}
		
		if(this.myVehicle.carChassis == null){
			throw (new Exception("Unable to initialize graphical simulation environment"));
		}
		
	}

}